<?php
class ModulePrivilege
{
    /**
     * @var ModulePrivilege
     */
    private static $instance;

    /**
     * @var ModulePrivilegeLogVoList
     */
    private $logList;

    /**
     * @var int
     */
    private $actorId;

    /**
     * @var ModulePrivilegeUserVo
     */
    private $actor;

    /**
     * @var ModulePrivilegeRoleVo
     */
    private $actorRole;

    /**
     * @var array()
     * <pre>
     * array(
     *      $privilegeId => ModulePrivilegeVo,
     *      ...
     * )
     * </pre>
     */
    private $actorRolePrivilegeList;

    /**
     * Get instance of ModulePrivilege.
     * @return ModulePrivilege
     */
    public static function get()
    {
        if (!self::$instance) {
            self::$instance = new ModulePrivilege();
        }
        return self::$instance;
    }

    /**
     * Initialize.
     */
    public function __construct()
    {
    }

    /**
     * Build session key.
     *
     * @param int $userId
     * @param int $createTime timestamp
     * @return string $sessionKey
     */
    protected function buildSessionKey($userId, $createTime)
    {
        return $userId . '^' . SzUtility::genMd5($createTime . ModulePrivilegeConst::SESSION_SALT);
    }

    /**
     * Build one encrypted password string.
     *
     * <pre>
     * $encryptedPwd = md5($rawPassword . PASSWORD_SALT)
     * </pre>
     *
     * @param string $password
     * @return string $encryptedPwd
     */
    protected function buildEncryptedPwd($password)
    {
        return SzUtility::genMd5($password . ModulePrivilegeConst::PASSWORD_SALT);
    }

    /**
     * Get actorId
     *
     * @return int
     */
    public function getActorId()
    {
        if ($this->actorId) {
            return $this->actorId;
        } else {
            return $this->checkLoginState();
        }
    }

    /**
     * Get actor
     *
     * @return ModulePrivilegeUserVo
     */
    public function getActor()
    {
        if ($this->actor) {
            return $this->actor;
        } else {
            return SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $this->getActorId());
        }
    }

    /**
     * Get actor role
     *
     * @return ModulePrivilegeRoleVo
     */
    public function getActorRole()
    {
        if ($this->actorRole) {
            return $this->actorRole;
        } else {
            return SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, $this->getActor()->getRoleId());
        }
    }

    /**
     * Get actor role privilege
     *
     * @return array
     */
    public function getActorRolePrivilege()
    {
        if ($this->actorRolePrivilegeList) {
            return $this->actorRolePrivilegeList;
        } else {
            return $this->getPrivilegeViaRole($this->getActorRole());
        }
    }

    /**
     * check login state
     *
     * @param boolean $errNotFound default true
     * @throws SzException 30413
     * @return int $actorId
     */
    public function checkLoginState($errNotFound = true)
    {
        $sessionId = SzParam::getCookieParam(ModulePrivilegeConst::SESSION_ID);
        $actorId = SzParam::getCookieParam(ModulePrivilegeConst::SESSION_ACTOR_ID);

        if (!$sessionId || !$actorId) {
            if ($errNotFound) {
                throw new SzException(30401, null, ModulePrivilegeConst::MODULE_NAME);
            } else {
                return null;
            }
        }

        /** @var ModulePrivilegeSessionVo $session */
        $session = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_SESSION, $actorId);
        if (!$session
            || $sessionId != $session->getSession()
            || $session->getExpired() < SzTime::getTime()) {
            if ($errNotFound) {
                throw new SzException(30401, null, ModulePrivilegeConst::MODULE_NAME);
            } else {
                return null;
            }
        }

        /** @var ModulePrivilegeLogVoList $logList */
        $this->logList = SzPersister::get()->getVoList($actorId, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_LOG);
        $this->actorId = $actorId;

        return $this->actorId;
    }

    /**
     * validate privilege
     *
     * @param string $reqName
     * @param string $reqParams json format string
     * @throws SzException 30402
     * @return void
     */
    public function validatePrivilege($reqName, $reqParams)
    {
        $actor = $this->getActor();
        if ($actor->getRoleId() == ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            return ;
        } else if ($actor->getRoleId() == ModulePrivilegeConst::NO_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $role = $this->getActorRole();
        if (is_null($role)
            || $role->getStatus() == ModulePrivilegeConst::DEFAULT_BOOL_OF_FALSE) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $privilegeId = null;
        $ownPrivilegeList = $this->getActorRolePrivilege();

        /** @var ModulePrivilegeVo $privilege */
        foreach ($ownPrivilegeList as $privilege) {
            if ($privilege->getRequestName() == $reqName) {
                $privilegeId = $privilege->getPrivilegeId();
                break;
            }
        }

        if (is_null($privilegeId)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $this->savePrivilegeLog($privilegeId, $reqParams);
    }

    /**
     * validate privilege
     *
     * @param int $privilegeId
     * @param string $reqParams json format string
     * @return void
     */
    public function savePrivilegeLog($privilegeId, $reqParams)
    {
        $log = new ModulePrivilegeLogVo(
            null, // logId
            $this->getActorId(), // userId
            $privilegeId, // privilegeId
            $reqParams, // detail
            SzTime::getTime(), // created
            true
        );
        $this->logList->addElement($log);
        SzPersister::get()->setVoList($this->logList);
    }

    /**
     * Create | refresh user's session record.
     *
     * @param int $userId
     * @return ModulePrivilegeSessionVo
     */
    protected function createSession($userId)
    {
        // prepare time
        $now = SzTime::getTime();
        $expire = $now + ModulePrivilegeConst::SESSION_LIFE_TIME;
        $sessionId = $this->buildSessionKey($userId, $now);

        /**
         * @var ModulePrivilegeSessionVo $session
         */
        $session = SzPersister::get()->getVo($userId, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_SESSION);
        if (!$session) {
            $session = new ModulePrivilegeSessionVo(
                $userId, // actorId
                $sessionId, // session
                $now, // created
                $expire, //expire
                true
            );
        } else {
            $session->setSession($sessionId);
            $session->setCreated($now);
            $session->setExpired($expire);
        }
        SzPersister::get()->setVo($session);

        // save in cookie
        SzParam::setCookieParam(ModulePrivilegeConst::SESSION_ID, $sessionId, $expire, SzConfig::get()->loadAppConfig('app', 'WEB_HOST'));
        SzParam::setCookieParam(ModulePrivilegeConst::SESSION_ACTOR_ID, $userId, $expire, SzConfig::get()->loadAppConfig('app', 'WEB_HOST'));

        return $session;
    }

    /**
     * check login state
     *
     * @param string $username
     * @return boolean
     */
    protected function validateUsername($username)
    {
        /**
         * @var ModulePrivilegeUserModel $userModel
         * @var ModulePrivilegeUserVo $user
         */
        $userModel = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER);
        $user = $userModel->select(array(
            1 => $username
        ));

        if (is_null($user)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * get privilege via role
     *
     * @param ModulePrivilegeRoleVo $role
     * @return array $ownPrivilegeList
     */
    protected function getPrivilegeViaRole($role)
    {
        // 获取角色的所有有效权限
        $ownPrivilegeList = array();
        foreach($role->getRawPrivilegeIds() as $privilegeId) {
            $this->getOwnPrivilege($privilegeId, $ownPrivilegeList);
        }

        return $ownPrivilegeList;
    }

    /**
     * 获得拥有的权限数据
     *
     * @param int $privilegeId
     * @param array $ownPrivilegeList
     * @return void
     */
    protected function getOwnPrivilege($privilegeId, &$ownPrivilegeList)
    {
        if (SzUtility::checkArrayKey($privilegeId, $ownPrivilegeList, true)) {
            return;
        }
        /** @var ModulePrivilegeVo $privilege */
        $privilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $privilegeId);
        if ($privilege->getStatus() == ModulePrivilegeConst::DEFAULT_BOOL_OF_FALSE) {
            return;
        }

        $ownPrivilegeList[$privilegeId] = $privilege;
        foreach ($privilege->getRawCIds() as $cId) {
            $this->getOwnPrivilege($cId, $ownPrivilegeList);
        }
    }

    /**
     * Reflection: get property value
     *
     * @param $className
     * @param $instance
     * @param $propertyName
     * @return mixed
     */
    protected static function getPropertyValue($className, $instance, $propertyName)
    {
        $reflector = new ReflectionProperty($className, $propertyName);
        $reflector->setAccessible(true);
        return $reflector->getValue($instance);
    }

    /**
     * Reflection: set method public
     *
     * @param $className
     * @param $methodName
     * @return ReflectionMethod
     */
    protected static function setMethodPublic($className, $methodName)
    {
        $reflector = new ReflectionMethod($className, $methodName);
        $reflector->setAccessible(true);
        return $reflector;
    }

    /**
     * Filter columns
     *
     * @param array $modelColumns
     * @param array $filterColumns default null
     * @return array
     */
    protected static function filterColumns($modelColumns, $filterColumns = null)
    {
        if (is_null($filterColumns)) {
            return $modelColumns;
        }

        foreach ($filterColumns as $filterColumn) {
            $key = array_shift(array_keys($modelColumns, $filterColumn));
            if (SzUtility::checkArrayKey($key, $modelColumns, true)) {
                unset($modelColumns[$key]);
            }
        }
        shuffle($modelColumns);
        return $modelColumns;
    }

    /**
     * Executes a select statement and <br/>
     * returns an array of associative arrays containing the results
     *
     * @param string $ormName
     * @param array $filterColumns default null
     * @param array $where default null
     * @return array
     */
    protected function select($ormName, $filterColumns = null, $where = null)
    {
        /** @var SzMySqlModel $sqlModel */
        $sqlModel = SzPersister::get()->getModel($ormName);

        $dbHandle = SzContextFactory::get()->getDb();
        $queryBuilder = SzContextFactory::get()->getQueryBuilder();

        // Reflection
        $modelTable = $this->getPropertyValue($ormName . 'Model', $sqlModel, 'table');
        $modelColumns = $this->filterColumns($sqlModel->getColumns(), $filterColumns);

        // prepare where
        $query = $queryBuilder->selectQuery($dbHandle->getDbName(), $modelTable, $modelColumns, $where);
        $result = $dbHandle->select($query);

        $data = array();
        foreach ($result as $row) {
            $data[$row[$sqlModel->getColumnName($sqlModel->getPkColumn())]] = $row;
        }
        return $data;
    }


    /**
     * Update orm data into database.
     *
     * @param string $ormName
     * @param array $data
     * @throws SzException 30416, 20604
     * @return array
     * <pre>
     * array(
     *     'succeed' => boolean,
     *     'data' => vo data array
     * )
     * </pre>
     */
    protected function update($ormName, $data)
    {
        $result = false;

        if (!is_array($data)) {
            throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
        }

        $model = SzPersister::get()->getModel($ormName);

        $cacheColumnName = $model->getColumnName($model->getCacheColumn());
        $shardColumnName = $model->getColumnName($model->getShardColumn());
        $pkColumnName = $model->getColumnName($model->getPkColumn());

        if ($shardColumnName == null) {
            if (!SzUtility::checkArrayKey(array($pkColumnName), $data, true)) {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
            $shardVal   = null;
        } else {
            if (!SzUtility::checkArrayKey(array($shardColumnName, $pkColumnName), $data, true)) {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
            $shardVal = $data[$shardColumnName];
        }

        $cacheVal   = $data[$cacheColumnName];
        $pkVal      = $data[$pkColumnName];

        /* @var SzAbstractVo $vo */
        $vo = null;
        if ($model->hasListMode()) {
            /* @var SzAbstractVoList $voList */
            $voList = $model->retrieve($shardVal, $cacheVal);
            $vo = $voList->getElement($pkVal);
        } else {
            $vo = $model->retrieve($shardVal, $cacheVal);
        }
        if (!$vo) {
            throw new SzException(20604, null, ModulePrivilegeConst::MODULE_NAME); // record not found
        }

        foreach ($model->getColumns() as $columnId => $columnName) { // loop according to orm columns order
            if (!SzUtility::checkArrayKey($columnName, $data, true)) {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
            if ($model->isJsonColumn($columnId)) {
                $columnValue = json_decode($data[$columnName], true);
            } else {
                $columnValue = $data[$columnName];
            }
            $model->setColumnValue($vo, $columnId, $columnValue);
        }

        if ($model->getDbType() == SzAbstractDb::DB_TYPE_MYSQL) {
            /* @var SzMySqlModel $model */
            list($affectedRows) = $model->update($vo);
            if ($affectedRows) {
                if ($model->hasListMode()) {
                    $shardKey = $model->getColumnValue($vo, $model->getShardColumn());
                    if (!$shardKey) {
                        throw new SzException(10509, null, ModulePrivilegeConst::MODULE_NAME);
                    }
                    $selectedList = $model->select($shardKey)->getList();
                    $model->setListOfVoCache(array($vo), $selectedList);
                } else {
                    $model->setVoCache($vo);
                }
                $result = true;
            }
        } else if ($model->getDbType() == SzAbstractDb::DB_TYPE_REDIS) {
            /* @var SzRedisModel $model */
            $model->save($vo);
            $result = true;
        }

        return array('succeed' => $result, 'data' => $vo->toArray());
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-*  API: profile info
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * user login
     *
     * @param string $username
     * @param string $password
     * @throws SzException 30402
     * @return ModulePrivilegeVo $user
     */
    public function login($username, $password)
    {
        // build password md5
        $password = $this->buildEncryptedPwd(base64_decode($password));

        // search user
        $userList = $this->select(
            ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, null, array(
                'username' => $username,
                'password' => $password
            )
        );
        // password or username is wrong
        if (empty($userList)) {
            throw new SzException(30402, $password, ModulePrivilegeConst::MODULE_NAME);
        } else {
            /**
             * @var ModulePrivilegeUserModel $userModel
             * @var ModulePrivilegeUserVo $user
             */
            $userData = array_pop($userList);
            $userModel = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER);
            $user = $userModel->convertAssocArrayToVo($userData);
        }
        // create actor session
        $this->createSession($user->getUserId());

        return $user;
    }

    /**
     * user logout
     *
     * @return boolean
     */
    public function logout()
    {
        if ($actorId = SzParam::getCookieParam(ModulePrivilegeConst::SESSION_ACTOR_ID)) {
            /** @var ModulePrivilegeSessionVo $session */
            $session = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_SESSION, $actorId);
            if ($session) {
                $session->setExpired(-1);
                SzPersister::get()->setVo($session);
            }

            // unset cookies
            SzParam::setCookieParam(ModulePrivilegeConst::SESSION_ID, null, -1, SzConfig::get()->loadAppConfig('app', 'WEB_HOST'));
            SzParam::setCookieParam(ModulePrivilegeConst::SESSION_ACTOR_ID, null, -1, SzConfig::get()->loadAppConfig('app', 'WEB_HOST'));
        }

        return true;
    }
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-*  API: USERS
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * validate user is super admin
     * ps: super admin can not be update or delete
     *
     * @param ModulePrivilegeUserVo $user default null
     * @throws SzException 30400
     * @return boolean
     */
    protected function validateSuperAdmin($user = null)
    {
        if (is_null($user)) {
            $user = $this->getActor();
        }
        if ($user->getRoleId() == ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Get user list
     *
     * @throws SzException 30400
     * @return array
     */
    public function getUserList()
    {
        return $this->select(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, array('password'));
    }

    /**
     * create user
     *
     * @param string $data
     * <pre>
     *  array(
     *      'username' => $username,
     *      'password' => $password,
     *      'name' => $name,
     *      'email' => $email
     *  )
     * </pre>
     * @throws SzException 30400
     * @return boolean
     */
    public function createUser($data)
    {
        $columns = array('username', 'password', 'name', 'email');
        $username = null;
        $password = null;
        $name = null;
        $email = null;
        foreach ($columns as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $$column = $data[$column];
            } else {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
        }

        // validate name length
        if (SzUtility::strUtf8CharLen($username) > 64
            || SzUtility::strUtf8CharLen($name) > 64) {
            throw new SzException(30404, null, ModulePrivilegeConst::MODULE_NAME);
        }

        // validate username is not exist
        if ($this->validateUsername($username)) {
            throw new SzException(30405, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $user = new ModulePrivilegeUserVo(
            null, // userId
            $name, // name
            $email, // email
            $username, // username
            $this->buildEncryptedPwd($password), // encrypt password
            ModulePrivilegeConst::NO_ROLE_ID, // roleId
            SzTime::getTime(), // created
            true
        );
        SzPersister::get()->setVo($user);
        return true;
    }

    /**
     * update password
     *
     * @param int $userId
     * @param string $oldPassword
     * @param string $newPassword
     * @throws SzException 30400 30403
     * @return boolean
     */
    public function updatePassword($userId, $oldPassword, $newPassword)
    {
        /** @var ModulePrivilegeUserVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $userId);
        if ($this->validateSuperAdmin($vo)
            && $this->getActor()->getRoleId() != ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }
        if ($vo->getPassword() != $this->buildEncryptedPwd(base64_decode($oldPassword))) {
            throw new SzException(30403, null, ModulePrivilegeConst::MODULE_NAME);
        }
        // change password
        $vo->setPropertyValue('password', $this->buildEncryptedPwd(base64_decode($newPassword)));
        SzPersister::get()->setVo($vo);

        return true;
    }

    /**
     * update user info
     *
     * @param int $userId
     * @param array $data
     * <pre>
     *  array(
     *      'name' => $name,
     *      'email' => $email
     *  )
     * </pre>
     * @throws SzException 30400
     * @return boolean
     */
    public function updateUser($userId, $data)
    {
        /** @var ModulePrivilegeUserVo $vo */
        $model = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER);
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $userId);

        if ($this->validateSuperAdmin($vo)
            && $this->getActor()->getRoleId() != ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        if (SzUtility::checkArrayKey('roleId', $data, true)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        // build Orm Data
        $ormData = array();
        foreach ($model->getColumns() as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $ormData[$column] = $data[$column];
            } else {
                $getValue = 'get' . ucfirst($column);
                $ormData[$column] = $vo->$getValue();
            }
        }
        return $this->update(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $ormData);
    }

    /**
     * change user role
     *
     * @param int $userId
     * @param int $roleId
     * @throws SzException 30400
     * @return boolean
     */
    public function changeRole($userId, $roleId)
    {
        /** @var ModulePrivilegeUserVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $userId);
        if ($this->validateSuperAdmin($vo)
            && $this->getActor()->getRoleId() != ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        if ($roleId == ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID
            || $vo->getRoleId() == ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        if (!SzUtility::checkArrayKey($roleId, $this->getRoleList(), true)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $vo->setRoleId($roleId);
        SzPersister::get()->setVo($vo);

        return true;
    }


    /**
     * remove user
     *
     * @param int $userId
     * @throws SzException 30400
     * @return boolean
     */
    public function deleteUser($userId)
    {
        /** @var ModulePrivilegeUserVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER, $userId);
        if ($this->validateSuperAdmin($vo)
            && $this->getActor()->getRoleId() != ModulePrivilegeConst::SUPER_ADMIN_ROLE_ID) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        /* @var ModulePrivilegeUserModel $model */
        $model = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_USER);
        list($affectedRows) = $model->delete($vo);
        if ($affectedRows) {
            $model->deleteVoCache($vo);
        }
        return true;
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-*  API: ROLE
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * validate role belong to actor
     *
     * @param ModulePrivilegeRoleVo $role
     * @return boolean
     */
    protected function validateRoleBelongToActor($role)
    {
        if ($role->getUserId() == $this->getActorId()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * filter privilegeIds belong to actor
     *
     * @param array $privilegeIds
     * @return array
     */
    protected function filterPrivilege($privilegeIds)
    {
        foreach ($privilegeIds as $key => $privilegeId) {
            if (!SzUtility::checkArrayKey($privilegeId, $this->getActorRolePrivilege(), true)) {
                unset($privilegeIds[$key]);
            }
        }
        shuffle($privilegeIds);
        return $privilegeIds;
    }

    /**
     * Get role list
     *
     * @return array
     */
    public function getRoleList()
    {
        if ($this->validateSuperAdmin()) {
            $where = array(
                'status' => ModulePrivilegeConst::DEFAULT_BOOL_OF_TRUE
            ); // 超级用户可以看到所有的角色
        } else {
            $where = array(
                'userId' => $this->actorId,
                'status' => ModulePrivilegeConst::DEFAULT_BOOL_OF_TRUE
            ); // 普通用户只可以看到自己建立的角色
        }
        return $this->select(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, null, $where);
    }

    /**
     * create role
     *
     * @param array $data
     * <pre>
     * array(
     *      'name' => $name
     * )
     * </pre>
     * @throws SzException 30400
     * @return boolean
     */
    public function createRole($data)
    {
        $columns = array('name');
        $name = null;
        foreach ($columns as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $$column = $data[$column];
            } else {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
        }

        $role = new ModulePrivilegeRoleVo(
            null, // roleId
            $this->getActorId(), // actorId
            $name, // name
            ModulePrivilegeConst::DEFAULT_BOOL_OF_TRUE, // status
            ModulePrivilegeConst::DEFAULT_JSON_STRING, // privilegeIds
            true
        );
        SzPersister::get()->setVo($role);
        return true;
    }

    /**
     * update role info
     *
     * @param int $roleId
     * @param array $data
     * <pre>
     *  array(
     *      'name' => $name
     *  )
     * </pre>
     * @throws SzException 30416 30400
     * @return boolean
     */
    public function updateRole($roleId, $data)
    {
        /** @var ModulePrivilegeRoleVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, $roleId);
        if (!$this->validateSuperAdmin()
            && !$this->validateRoleBelongToActor($vo)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        $model = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE);

        // build Orm Data
        $ormData = array();
        foreach ($model->getColumns() as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $ormData[$column] = $data[$column];
            } else {
                $getValue = 'get' . ucfirst($column);
                $ormData[$column] = $vo->$getValue();
            }
        }
        return $this->update(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, $ormData);
    }

    /**
     * remove role
     *
     * @param int $roleId
     * @throws SzException 30400
     * @return boolean
     */
    public function deleteRole($roleId)
    {
        /** @var ModulePrivilegeRoleVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, $roleId);
        if (!$this->validateSuperAdmin()
            && !$this->validateRoleBelongToActor($vo)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }
        $vo->setStatus(ModulePrivilegeConst::DEFAULT_BOOL_OF_FALSE);
        SzPersister::get()->setVo($vo);

        return true;
    }

    /**
     * change role privilege
     *
     * @param int $roleId
     * @param array $privilegeIds
     * @throws SzException 30400
     * @return boolean
     */
    public function changeRolePrivilege($roleId, $privilegeIds)
    {
        /** @var ModulePrivilegeRoleVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE_ROLE, $roleId);
        if (!$this->validateSuperAdmin($this->getActor())) {
            if (!$this->validateRoleBelongToActor($vo)) {
                throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
            }
            $privilegeIds = $this->filterPrivilege($privilegeIds);
        }

        $vo->setPrivilegeIds($this->getValidPrivilegeId($privilegeIds));
        SzPersister::get()->setVo($vo);
        return true;
    }

    /**
     * Get valid privilegeId
     * valid privilegeId need meet one of the two conditions
     * 1. pIds not in chosen privilegeIds
     * 2. privilegeId is not in ownPrivilegeIds
     *
     * @param array $privilegeIds
     * @return array $ownPrivilegeIds
     */
    protected function getValidPrivilegeId($privilegeIds)
    {
        $ownPrivilegeIds = array();
        foreach($privilegeIds as $privilegeId) {
            if (in_array($privilegeId, $ownPrivilegeIds)) {
                continue;
            }
            /** @var ModulePrivilegeVo $privilege */
            $privilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $privilegeId);
            if ($privilege->getStatus() == ModulePrivilegeConst::DEFAULT_BOOL_OF_FALSE) {
                continue;
            }

            $pIdIsChosen = false;
            foreach ($privilege->getRawPIds() as $pId) {
                if (in_array($pId, $privilegeIds)) {
                    $pIdIsChosen = true;
                }
            }
            if (!$pIdIsChosen) {
                $ownPrivilegeIds[] = $privilegeId;
            }
        }

        return $ownPrivilegeIds;
    }

    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    //-*  API: Privilege
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    /**
     * validate privilege belong to actor role
     *
     * @param int $privilegeId
     * @return boolean
     */
    protected function validatePrivilegeBelongToActorRole($privilegeId)
    {
        if (!$this->validateSuperAdmin()) {
            if (!SzUtility::checkArrayKey($privilegeId, $this->getActorRolePrivilege(), true)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get privilege list
     *
     * @throws SzException 30400
     * @return array
     */
    public function getPrivilegeList()
    {
        if ($this->validateSuperAdmin()) {
            $where = null; // 超级用户可以看到所有的权限
        } else {
            $where = array(
                'privilegeId' => array_keys($this->getActorRolePrivilege())
            ); // 普通用户只可以看到自己角色所属的权限
        }
        return $this->select(ModulePrivilegeConst::ORM_NAME_PRIVILEGE, null, $where);
    }

    /**
     * Get privilege tree
     *
     * @return array
     */
    public function getPrivilegeTreeList()
    {
        return $this->getPrivilegeChildTreeList($this->getPrivilegeTree());
    }

    /**
     * Get privilege child tree
     *
     * @param array $privilegeTree
     * @param array $privilegeTreeList default array()
     * @return array $privilegeTreeList
     */
    protected function getPrivilegeChildTreeList($privilegeTree, &$privilegeTreeList = array())
    {
        foreach ($privilegeTree as $privilegeData) {
            // 删除不必要的显示数据
            $childList = $privilegeData['childList'];
            unset($privilegeData['childList']);

            // child level prefix
            $childPrefix = '';
            for ($i = 0; $i < $privilegeData['childLevel']; $i++) {
                $childPrefix.= '|-- ';
            }
            $privilegeData['childPrefix'] = $childPrefix;

            $privilegeTreeList[$privilegeData['privilegeId']] = $privilegeData;
            $this->getPrivilegeChildTreeList($childList, $privilegeTreeList);
        }
        return $privilegeTreeList;
    }

    /**
     * Get privilege tree
     *
     * @return array $searchResult
     */
    protected function getPrivilegeTree()
    {
        /** @var ModulePrivilegeModel $privilegeModel */
        $privilegeModel = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE);
        $privilegeList = $this->getPrivilegeList();

        $privilegeTree = array();
        foreach ($privilegeList as $privilegeData) {
            /** @var ModulePrivilegeVo $privilege */
            $privilege = $privilegeModel->convertAssocArrayToVo($privilegeData);
            if (empty($privilege->getRawPIds())
                && $privilege->getStatus() == ModulePrivilegeConst::DEFAULT_BOOL_OF_TRUE) { // 找到第一层的权限后，再通过递归找到子权限
                $cIds = $privilege->getRawCIds();
                $privilegeId = $privilege->getPrivilegeId();
                $privilege = $privilege->toArray();
                $childLevel = 0;
                $privilege['childLevel'] = 0;
                $privilege['childList'] = $this->getChildPrivilege($cIds, $privilegeList, $privilegeModel, $childLevel);
                $privilegeTree[$privilegeId] = $privilege;
            }
        }
        return $privilegeTree;
    }

    /**
     * Get child privilege tree
     *
     * @param array $privilegeIds
     * @param array $privilegeList
     * @param ModulePrivilegeModel $privilegeModel
     * @param int $childLevel default = 0;
     * @return array
     */
    protected function getChildPrivilege($privilegeIds, $privilegeList, $privilegeModel, $childLevel = 0)
    {
        $childLevel++;
        $privilegeTree = array();
        if (!empty($privilegeIds)) {
            foreach ($privilegeIds as $privilegeId) {
                /** @var ModulePrivilegeVo $privilege */
                $privilege = $privilegeModel->convertAssocArrayToVo($privilegeList[$privilegeId]);
                $privilegeId = $privilege->getPrivilegeId();
                $cIds = $privilege->getRawCIds();
                $privilege = $privilege->toArray();
                $privilege['childLevel'] = $childLevel;
                $privilege['childList'] = $this->getChildPrivilege($cIds, $privilegeList, $privilegeModel, $childLevel);
                $privilegeTree[$privilegeId] = $privilege;
            }
        }
        return $privilegeTree;
    }

    /**
     * create privilege
     *
     * @param array $data
     * <pre>
     *  array(
     *      'name' => $name
     *      'requestName' => $requestName
     *  )
     * </pre>
     * @throws SzException 30416
     * @return boolean
     */
    public function createPrivilege($data)
    {
        $columns = array('name', 'requestName');
        $name = null;
        $requestName = null;
        foreach ($columns as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $$column = $data[$column];
            } else {
                throw new SzException(30416, null, ModulePrivilegeConst::MODULE_NAME); // invalid $data
            }
        }
        $privilege = new ModulePrivilegeVo(
            null, // privilegeId
            ModulePrivilegeConst::DEFAULT_JSON_STRING, // pIds
            ModulePrivilegeConst::DEFAULT_JSON_STRING, // cIds
            $name, // name
            $requestName, // requestName
            ModulePrivilegeConst::DEFAULT_BOOL_OF_TRUE, // status
            true
        );
        SzPersister::get()->setVo($privilege);
        return true;
    }

    /**
     * update privilege info
     *
     * @param int $privilegeId
     * @param array $data
     * <pre>
     *  array(
     *      'name' => $name
     *      'requestName' => $requestName
     *  )
     * </pre>
     * @throws SzException 30400 30416
     * @return boolean
     */
    public function updatePrivilege($privilegeId, $data)
    {
        if (!$this->validatePrivilegeBelongToActorRole($privilegeId)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        /** @var ModulePrivilegeVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $privilegeId);
        $model = SzPersister::get()->getModel(ModulePrivilegeConst::ORM_NAME_PRIVILEGE);

        // build Orm Data
        $ormData = array();
        foreach ($model->getColumns() as $column) {
            if (SzUtility::checkArrayKey($column, $data, true)) {
                $ormData[$column] = $data[$column];
            } else {
                $getValue = 'get' . ucfirst($column);
                $ormData[$column] = $vo->$getValue();
            }
        }
        return $this->update(ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $ormData);
    }

    /**
     * remove privilege
     *
     * @param int $privilegeId
     * @throws SzException 30400 30416
     * @return boolean
     */
    public function deletePrivilege($privilegeId)
    {
        if (!$this->validatePrivilegeBelongToActorRole($privilegeId)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        /** @var ModulePrivilegeVo $vo */
        $vo = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $privilegeId);
        // update child privilege
        foreach ($vo->getRawCIds() as $cId) {
            /** @var ModulePrivilegeVo $childPrivilege */
            $childPrivilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $cId);
            $childPrivilege->removePId($privilegeId);
            SzPersister::get()->setVo($childPrivilege);
        }

        // update parent privilege
        foreach ($vo->getRawPIds() as $pId) {
            /** @var ModulePrivilegeVo $parentPrivilege */
            $parentPrivilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $pId);
            $parentPrivilege->removeCId($privilegeId);
            SzPersister::get()->setVo($parentPrivilege);
        }

        $vo->setStatus(ModulePrivilegeConst::DEFAULT_BOOL_OF_FALSE);
        SzPersister::get()->setVo($vo);

        return true;
    }

    /**
     * 修改权限的父节点
     *
     * @param int $privilegeId
     * @param array $pIds
     * @return boolean
     * @throws SzException 30400
     */
    public function changePrivilege($privilegeId, $pIds)
    {
        if (!$this->validatePrivilegeBelongToActorRole($privilegeId)) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        if (!$pIds && $pIds !== array()) {
            throw new SzException(30400, null, ModulePrivilegeConst::MODULE_NAME);
        }

        /** @var ModulePrivilegeVo $privilege */
        $privilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $privilegeId);
        $oldPIds = $privilege->getRawPIds();
        $privilege->setPIds($pIds);
        SzPersister::get()->setVo($privilege);

        // handle update parent privilege cIds
        foreach ($oldPIds as $pId) {
            /** @var ModulePrivilegeVo $parentPrivilege */
            $parentPrivilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $pId);
            $parentPrivilege->removeCId($privilegeId);
            SzPersister::get()->setVo($parentPrivilege);
        }

        // handle update parent privilege cIds
        foreach ($pIds as $pId) {
            /** @var ModulePrivilegeVo $parentPrivilege */
            $parentPrivilege = SzPersister::get()->getVo(null, ModulePrivilegeConst::ORM_NAME_PRIVILEGE, $pId);
            $parentPrivilege->addCIds($privilegeId);
            SzPersister::get()->setVo($parentPrivilege);
        }

        return true;
    }

    /**
     * Get logger list
     *
     * @param int $actorId
     * @return array
     */
    public function getLoggerList($actorId)
    {
        $where = array(
            'actorId' => $actorId,
        );
        return $this->select(ModulePrivilegeConst::ORM_NAME_PRIVILEGE_LOG, null, $where);
    }
}